class Scheduler {
    static STATUS = {
        PENDING: 1,
        STARTED: 2,
        PAUSED: 3,
        STOPED: 4,
        ERROR: 5,
    }
    constructor(target, option) {
        const { start = 0, duration = 1000, callback, ext = {} } = option;
        this.target = target;
        this.start = start;
        this.duration = duration;
        this.callback = callback
        this.ext = ext;
        this.status = Scheduler.STATUS.PENDING;
    }

    run(timer) {
        if (this.callback) {
            this.callback(this.target, {
                timer,
                ext: this.ext,
                duration: this.duration,
            });
        }
    }

    get end() {
        return this.start + this.duration;
    }
}
const easingFuncs = {
    linear: function (k) {
        return k;
    },
    quadraticIn: function (k) {
        return k * k;
    },
    quadraticOut: function (k) {
        return k * (2 - k);
    },
    quadraticInOut: function (k) {
        if ((k *= 2) < 1) { return 0.5 * k * k; }
        return -0.5 * (--k * (k - 2) - 1);
    },
    cubicIn: function (k) {
        return k * k * k;
    },
    cubicOut: function (k) {
        return --k * k * k + 1;
    },
    cubicInOut: function (k) {
        if ((k *= 2) < 1) { return 0.5 * k * k * k; }
        return 0.5 * ((k -= 2) * k * k + 2);
    },
    quarticIn: function (k) {
        return k * k * k * k;
    },
    quarticOut: function (k) {
        return 1 - (--k * k * k * k);
    },
    quarticInOut: function (k) {
        if ((k *= 2) < 1) { return 0.5 * k * k * k * k; }
        return -0.5 * ((k -= 2) * k * k * k - 2);
    },
    quinticIn: function (k) {
        return k * k * k * k * k;
    },
    quinticOut: function (k) {
        return --k * k * k * k * k + 1;
    },
    quinticInOut: function (k) {
        if ((k *= 2) < 1) { return 0.5 * k * k * k * k * k; }
        return 0.5 * ((k -= 2) * k * k * k * k + 2);
    },
    sinusoidalIn: function (k) {
        return 1 - Math.cos(k * Math.PI / 2);
    },
    sinusoidalOut: function (k) {
        return Math.sin(k * Math.PI / 2);
    },
    sinusoidalInOut: function (k) {
        return 0.5 * (1 - Math.cos(Math.PI * k));
    },
    exponentialIn: function (k) {
        return k === 0 ? 0 : Math.pow(1024, k - 1);
    },
    exponentialOut: function (k) {
        return k === 1 ? 1 : 1 - Math.pow(2, -10 * k);
    },
    exponentialInOut: function (k) {
        if (k === 0) {
            return 0;
        }
        if (k === 1) {
            return 1;
        }
        if ((k *= 2) < 1) {
            return 0.5 * Math.pow(1024, k - 1);
        }
        return 0.5 * (-Math.pow(2, -10 * (k - 1)) + 2);
    },
    circularIn: function (k) {
        return 1 - Math.sqrt(1 - k * k);
    },
    circularOut: function (k) {
        return Math.sqrt(1 - (--k * k));
    },
    circularInOut: function (k) {
        if ((k *= 2) < 1) { return -0.5 * (Math.sqrt(1 - k * k) - 1); }
        return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
    },
    elasticIn: function (k) {
        var s;
        var a = 0.1;
        var p = 0.4;
        if (k === 0) { return 0; }
        if (k === 1) { return 1; }
        if (!a || a < 1) { a = 1; s = p / 4; }
        else { s = p * Math.asin(1 / a) / (2 * Math.PI); }
        return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
    },
    elasticOut: function (k) {
        var s;
        var a = 0.1;
        var p = 0.4;
        if (k === 0) { return 0; }
        if (k === 1) { return 1; }
        if (!a || a < 1) { a = 1; s = p / 4; }
        else { s = p * Math.asin(1 / a) / (2 * Math.PI); }
        return (a * Math.pow(2, -10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1);
    },
    elasticInOut: function (k) {
        var s;
        var a = 0.1;
        var p = 0.4;
        if (k === 0) { return 0; }
        if (k === 1) { return 1; }
        if (!a || a < 1) { a = 1; s = p / 4; }
        else { s = p * Math.asin(1 / a) / (2 * Math.PI); }
        if ((k *= 2) < 1) {
            return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
        }
        return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;

    },

    // 在某一动画开始沿指示的路径进行动画处理前稍稍收回该动画的移动
    backIn: function (k) {
        var s = 1.70158;
        return k * k * ((s + 1) * k - s);
    },
    backOut: function (k) {
        var s = 1.70158;
        return --k * k * ((s + 1) * k + s) + 1;
    },
    backInOut: function (k) {
        var s = 1.70158 * 1.525;
        if ((k *= 2) < 1) { return 0.5 * (k * k * ((s + 1) * k - s)); }
        return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
    },

    // 创建弹跳效果
    bounceIn: function (k) {
        return 1 - easingFuncs.bounceOut(1 - k);
    },
    bounceOut: function (k) {
        if (k < (1 / 2.75)) { return 7.5625 * k * k; }
        else if (k < (2 / 2.75)) { return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75; }
        else if (k < (2.5 / 2.75)) { return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375; }
        else { return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375; }
    },
    bounceInOut: function (k) {
        if (k < 0.5) { return easingFuncs.bounceIn(k * 2) * 0.5; }
        return easingFuncs.bounceOut(k * 2 - 1) * 0.5 + 0.5;
    }
};
export default class Animate {
    static STATUS = {
        PENDING: 1,
        RUNNING: 2,
        PAUSED: 3,
        STOPED: 4,
        ERROR: 5,
    }
    constructor(duration) {
        this.duration = duration;
        this.schedulers = [];
        this.animateId = null;
        this.startTimeStamp = -1;
        this.timeline = 0;
    }
    
    registerScheduler(target, option) {
        const { duration = 0, start = 0 } = option;
        if (start + duration > this.duration) {
            this.duration = start + duration;
        }
        const scheduler = {
            key: new Date().getTime(),
            scheduler: new Scheduler(target, option),
        };
        this.schedulers.push(scheduler);
        return scheduler.key;
    }
    
    removeScheduler(key) {
        const schedulerIndex = this.schedulers.findIndex(vo => vo.key === key);
        if (key >= 0) {
            this.schedulers.splice(schedulerIndex, 1);
        }
    }

    run() {
        if (this.status === Animate.STATUS.RUNNING) {
            this.onTick && this.onTick(this.timeline);
            if (this.timeline < this.duration) {
                // 遍历当前调度器
                for (let i = 0; i < this.schedulers.length; i++) {
                    const scheduler = this.schedulers[i].scheduler;
                    if (
                        scheduler.start < this.timeline
                        && scheduler.end > this.timeline
                    ) {
                        const timer = this.timeline - scheduler.start;
                        scheduler.run(timer, this.timeline);
                    }
                }
                const now = Date.now();
                this.timeline = now - this.startTimeStamp;
                this.animateId = requestAnimationFrame(() => this.run());
            } else {
                this.status = Animate.STATUS.STOPED;
                this.onStop  && this.onStop();
            }
        }
    }

    start() {
        this.status = Animate.STATUS.RUNNING;
        this.timeline = -1;
        this.startTimeStamp = Date.now();
        this.run();
    }

    pause() {
        if (this.status === Animate.STATUS.RUNNING) {
            if (this.animateId) {
                cancelAnimationFrame(this.animateId);
                this.animateId = null;
                this.status = Animate.STATUS.PAUSED;
            }
        }
        
    }

    resume() {
        this.status = Animate.STATUS.RUNNING;
        this.startTimeStamp = Date.now() - this.timeline;
        this.run();
    }

    snapshot(timer) {
        const schedulerMap = {};
        for (let i = 0; i < this.schedulers.length; i++) {
            const scheduler = this.schedulers[i];
            if (!schedulerMap[scheduler.scheduler.target]) {
                schedulerMap[scheduler.scheduler.target] = [];
            }
            schedulerMap[scheduler.scheduler.target].push(scheduler);
        }
        let targets = Object.keys(schedulerMap);
        for (let target of targets) {
            schedulerMap[target].sort((a, b) => a.scheduler.start - b.scheduler.start);
            for (let i = 0; i < schedulerMap[target].length; i++) {
                const scheduler = schedulerMap[target][i].scheduler;
                if (scheduler.end < timer) {
                    scheduler.run(scheduler.duration);
                } else {
                    let targetTimer = timer - scheduler.start;
                    scheduler.run(targetTimer);
                }
            }
        }
    }

    stop() {
        
    }
    
}
Animate.easingFuncs = easingFuncs;
